Skip to content

2650. 设计可取消函数

2650. 设计可取消函数

ts
function cancellable<T>(
  generator: Generator<Promise<any>, T, unknown>
): [() => void, Promise<T>] {
  let cancelled = false

  const cancel = () => {
    cancelled = true
  }

  const promise = new Promise<T>(async (resolve, reject) => {
    try {
      let result = generator.next()

      while (!result.done) {
        await result.value

        if (cancelled) return reject('Cancelled')

        result = generator.next()
      }

      if (cancelled) return reject('Cancelled')

      resolve(result.value)
    } catch (err) {
      if (cancelled) return reject('Cancelled')
      reject(err)
    }
  })

  return [cancel, promise]
}

为什么用 Generator

这个问题的输入是一个 Generator 函数,它 yield 出 Promise。Generator 天然支持「执行一步 → 等待 → 检查状态 → 继续执行」,恰好对应取消语义的实现需求。

如果不使用 Generator,在普通的 Promise 链中插入取消检查会非常困难——Promise 一旦创建就无法从外部中止。Generator 把异步流程的控制权交还给了调用方代码,使得每一步之后都可以插入取消判断。

取消的时机

设计上有两个检查点:

  1. 每个 yield 出的 Promise resolve 之后
  2. Generator 完全执行完毕时

这意味着取消不中止正在飞行的 Promise——已经 await 的 Promise 会继续跑完。取消只阻止后续步骤的执行。这和浏览器中 AbortController 的行为一致——真正的网络请求取消需要底层支持(fetch 配合 signal),而不是封装层能做到的。

错误处理

catch 分支也需要检查 cancelled。如果 Generator 内部抛错,但外部已经取消了,应该返回 Cancelled 而不是抛原始错误——因为调用方已经不关心结果了。